#ifndef SCENE_H
#define SCENE_H

#include <QtGui>

#include "DataSource.h"

#include "camera.h"
#include "grid.h"
#include "container.h"
#include "Singleton.h"

class Context
{
public:
	Context() : cameraNoPitch(true), cameraNoYaw(true), eventsEnabled(true), interruptsEnabled(true), listEnabled(false), listContainer(NULL) {};
	
	bool drawEvents() const { return eventsEnabled; } // && cameraNoPitch && cameraNoYaw; }
	bool hitEvents() const { return drawEvents(); }
	
	bool drawInterrupts() const { return interruptsEnabled; } // && cameraNoPitch && cameraNoYaw; }
	bool hitInterrupts() const { return drawInterrupts(); }
	
	bool drawList() const { return listEnabled && cameraNoPitch && cameraNoYaw; }
	bool hitList() const { return drawList(); }
	
	bool screenSpaceLayout() const { return cameraNoPitch && cameraNoYaw; }
	
	void selectContainer(Container* container);
	
	bool cameraNoPitch;
	bool cameraNoYaw;
		
	bool eventsEnabled;
	bool interruptsEnabled;
	
	bool listEnabled;
	Container* listContainer;
	
	CameraController* cameraController;
};

class ContextUser : public QObject
{
	Q_OBJECT
	
public:
	Context* getContext() { return context; }
	void setContext(Context* in) { context = in; }
	
protected:
	Context* context;
};

class Interrupts : public ContextUser
{
public:
    Interrupts() { states.setName("Interrupts"); states.setLabeled(true); }
    
    void addState(State* state);
    
    const BoundingBox& boundingBox() const { return states.boundingBox; }
    const BoundingBox& labeledBoundingBox() const { return states.labeledBoundingBox; }
    void getPosition(Vec3f out) const { states.getPosition(out); }
    void setPosition(Vec3f position) { states.setPosition(position); }
	void setScaling(Vec3f scale) { states.setScaling(scale); }
    
    void setupForDrawing();
	void layoutWithCamera(Camera& camera) { states.layoutWithCamera(camera); }
    void drawVBOWithCamera(Camera& camera);
	void drawBackgroundWithCamera(Camera& camera);
    void drawVAWithCamera(Camera& camera);
	void drawTextWithCamera(Camera& camera);
    
	void updateColors() { states.reassignColors(); }
	
    QVector<Element*> pickElementsWithRay(const Ray& ray) const { return states.pickElementsWithRay(ray); }
    void toggleElementSelected(Element* element) { states.toggleElementSelected(element); }
	
	bool mouseDownWithRay(const Ray& ray, QMouseEvent* event);
	
	QList<Element*> getSelection() const { return states.getSelection(); }
	bool clearSelection() { return states.clearSelection(); }
    
private:
    Container states;
};

class Thread : public ContextUser
{
public:
    Thread(int id, const QString& name = "unknown");
    
    void addState(State* state);
	void addMutex(Mutex* mutex);
    
    int getId() const { return id; }
    const QString& getName() const { return name; }
    
    const BoundingBox& boundingBox() const { return (drawMutexes ? mutexes.boundingBox : states.boundingBox); }
    const BoundingBox& labeledBoundingBox() const { return (drawMutexes ? mutexes.labeledBoundingBox : states.labeledBoundingBox); }
    void getPosition(Vec3f out) const { states.getPosition(out); }
    void setPosition(Vec3f position);
	void setScaling(Vec3f scale) { states.setScaling(scale); mutexes.setScaling(scale); }
    
    void setupForDrawing();
	void layoutWithCamera(Camera& camera) { states.layoutWithCamera(camera); mutexes.layoutWithCamera(camera); }
    void drawVBOWithCamera(Camera& camera);
	void drawBackgroundWithCamera(Camera& camera);
    void drawVAWithCamera(Camera& camera);
	void drawTextWithCamera(Camera& camera);
	
	void updateColors() { states.reassignColors(); mutexes.reassignColors(); }
    
    QVector<Element*> pickElementsWithRay(const Ray& ray) const { return states.pickElementsWithRay(ray); }
    void toggleElementSelected(Element* element) { states.toggleElementSelected(element); }
	
	bool mouseDownWithRay(const Ray& ray, QMouseEvent* event);
	
	void dummySearch(QString state);
	void clearSearch();
	bool clearSelection() { return (states.clearSelection() && mutexes.clearSelection()); }
	
	QList<Element*> getSelection() const;
	BoundingBox getSelectionBoundingBox() const;
	
	float getLength() { float statesLength = states.boundingBox.width(); float mutexesLength = mutexes.boundingBox.width(); return (statesLength > mutexesLength ? statesLength : mutexesLength); }
	
	void setLength(float newLength) { states.setLength(newLength); mutexes.setLength(newLength); }
	
	bool setDrawMutexes(bool enabled);
	
private:
    int id;
    QString name;
    
	bool drawMutexes;
	
    Container states;
	Container mutexes;
};

class Events : public ElementsGrid, public ContextUser
{
public:
	Events() {}
	
	bool mouseDownWithRay(const Ray& ray, QMouseEvent* event);
};

class Trace : public ContextUser
{
	Q_OBJECT
	
public:
    Trace() {}
    
    void setContext(Context* context);
	
    void addThread(Thread* thread);
    void addEvent(Event* event);
    
    Interrupts& getInterrupts() { return interrupts; }
    Thread& getThread(unsigned id) { return *(threads[id]); }
    
    void setupForDrawing();
    void draw(CameraController* cameraController);

    QVector<Element*> pickElementsWithRay(const Ray& ray);
	
    bool mouseDownWithRay(const Ray& ray, QMouseEvent* event);
	
		QList<Element*> getSelection() const;
		BoundingBox getSelectionBoundingBox() const;
		
    void setDrawMutexesForAll(bool enabled);
    
public slots:
	void updateColors();
	void dummySearch(QString state);
	void clearSearch();
	bool clearSelection();
	void focusSelection() { context->cameraController->moveTo(getSelection(), getSelectionBoundingBox()); }
	void clearSelectionOrSearch() { if (!clearSelection()) clearSearch(); }
	void moveToPrevious() { Vec3f pos; getSelectionBoundingBox().getLeftCenter(pos); context->cameraController->moveToTraceHorizontalPos(pos[0]); }
	void moveToNext() { Vec3f pos; getSelectionBoundingBox().getRightCenter(pos); context->cameraController->moveToTraceHorizontalPos(pos[0]); }
	
protected:
    void setupInitialLayout();
	void setupScaling(CameraController* cameraController);
    void setupLayout(CameraController* cameraController);
    
protected:
    QList<Thread*> threads;
    Interrupts interrupts;
    Events events;
    Grid grid;
	ElementsDoubleList list;
    
    QList<Thread*> orderedThreads;
};

class Scene : public QObject
{
	Q_OBJECT
	
public:
    Scene(DataSource* dataSource);
    
    void createDummy();
	
    void setupForDrawing();
    
    void draw(CameraController* cameraController);
    
    bool mouseDownWithRay(const Ray& ray, QMouseEvent* event);
	
    void switchView() { timeBased = !timeBased; }

    bool isTimeBased() const {return timeBased;}
	
    Trace& getEventBasedTrace() { return eventBasedTrace; }
    Trace& getTimeBasedTrace() { return timeBasedTrace; }
    
    void setDrawMutexesForAll(bool enabled);
	
public slots:
	void updateColors() { eventBasedTrace.updateColors(); timeBasedTrace.updateColors(); }
	void dummySearch(QString state) { eventBasedTrace.dummySearch(state); timeBasedTrace.dummySearch(state); emit selectionDescriptionChanged(getSelectionDescription());}
	void focusSelection() { getTrace().focusSelection(); }
	void clearSelectionOrSearch() { getTrace().clearSelectionOrSearch(); emit selectionDescriptionChanged(getSelectionDescription());}
	void moveToPrevious() { getTrace().moveToPrevious(); }
	void moveToNext() { getTrace().moveToNext(); }

signals:
	void selectionDescriptionChanged(const QMap<QString, QString>& data);
	
private:
    Trace& getTrace() { return (timeBased ? timeBasedTrace : eventBasedTrace); }
	QMap<QString, QString> getSelectionDescription();
    
private:
    Trace eventBasedTrace;
    Trace timeBasedTrace;
    bool timeBased;
	
	bool setup;
	
	DataSource* dataSource;
};

#endif // SCENE_H
